﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CodeRepositoryConsole.OOP
{

    /// <summary>
    /// Equals Test Driver
    /// </summary>
    public static class EqualityTestDriver
    {
        public static void ReferenceEquals()
        {
            EqualityReferenceType a1 = new EqualityReferenceType("a", "b", "c1");
            EqualityReferenceType a2 = new EqualityReferenceType("a", "b", "c2");

            Console.WriteLine("equality operator: a1 == a2 is {0}", a1 == a2);

            Console.WriteLine("IEquatable<T>.Equals: a1.Equals(a2) is {0}", a1.Equals(a2));

            Console.WriteLine("Object.Equals(Object): a1.Equals((object)a2) is {0}", a1.Equals((object)a2));
        }

        public static void ValueTypeEquals()
        {
            EqualityReferenceType b1 = new EqualityReferenceType("a", "b", "c1");
            EqualityReferenceType b2 = new EqualityReferenceType("a", "b", "c2");

            EqualityValueType a1 = new EqualityValueType(123, b1);
            EqualityValueType a2 = new EqualityValueType(123, b2);

            // Console.WriteLine("equality operator: a1 == a2 is {0}", a1 == a2); why cann't use == operator

            Console.WriteLine("IEquatable<T>.Equals: a1.Equals(a2) is {0}", a1.Equals(a2));

            Console.WriteLine("Object.Equals(Object): a1.Equals((object)a2) is {0}", a1.Equals((object)a2));
        }
    }


    /// <summary>
    /// Reference Type Equal demonstrate
    /// </summary>
    public class EqualityReferenceType : IEquatable<EqualityReferenceType>
    {
        /*+++++++++++++++++++++++++++++++++++++++
         GuideLines for Reference Types
         You should not override Equal on a mutable reference type.
         This is because override Equals require that you alse override the GetHashCode method,
         as discussed in the previous section. This means that the hash code of an instance of
         a mutable reference type can change during its lifetime, which can cause the object to 
         be lost in a hash table. 

            So the property of this class only has get.

         refer:https://msdn.microsoft.com/en-us/library/bsc2ak47(v=vs.110).aspx
         -----------------------------------------*/

        /// <summary>
        /// only has get accessor because it should not be mutable
        /// </summary>
        public string A { get; }
        public string B { get; }
        public string C { get; }

        public EqualityReferenceType(string a, string b, string c)
        {
            this.A = a;
            this.B = b;
            this.C = c;
        }

        /// <summary>
        /// implementing IEquatable<T>.Equals method
        /// guideline 3:
        /// You should consider implementing the IEquatable<T> interface to support strongly type tests for equality.
        /// Your IEquatable<T> implemention should return results that are consistent with Equals.
        /// </summary>
        /// <param name="other"></param>
        /// <returns></returns>
        public bool Equals(EqualityReferenceType other)
        {
            // x.Equals(null) return false.
            if (ReferenceEquals(null, other))
            {
                return false;
            }

            if (ReferenceEquals(this, other))
            {
                return true;
            }

            return Equals(other.A, A) && Equals(other.B, B);
        }

        /// <summary>
        /// override Object.Equals method
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj))
            {
                return false;
            }

            if (ReferenceEquals(this, obj))
            {
                return true;
            }

            // check exact same type prevent broken symmetric property
            if (obj.GetType() != typeof(EqualityReferenceType))
            {
                return false;
            }

            return Equals((EqualityReferenceType)obj);

        }

        /// <summary>
        /// guideline 2:
        /// Type that override Equal(Object) must also override GetHashCode; otherwise, hash tables might not work correctly.
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
        {
            unchecked
            {
                return ((A != null ? A.GetHashCode() : 0) * 397) ^ (B != null ? B.GetHashCode() : 0);
            }
        }

        public static bool operator ==(EqualityReferenceType left, EqualityReferenceType right)
        {
            return Equals(left, right);
        }

        public static bool operator !=(EqualityReferenceType left, EqualityReferenceType right)
        {
            // when the Object.Equals(Object, Object) method is passed two StringBuilder objects, 
            // the default Object.Equals(Object) method is called.
            return !Equals(left, right);
        }

    }

    /// <summary>
    /// Value Type Equal demonstrate
    /// </summary>
    public struct EqualityValueType
    {
        public int A { get; }

        /// <summary>
        /// guideline 1:
        /// If you are defining a value type that includes one or more fields whose values are reference types, 
        /// you should override Equals(Object). The Equals(Object) implementation provided by ValueType performs 
        /// a byte-by-byte comparison for value types whose fields are all value types, but it uses reflection to 
        /// perform a field-by-field comparison of value types whose fields include reference types. 
        /// </summary>
        public EqualityReferenceType B { get; }

        public EqualityValueType(int a, EqualityReferenceType b)
        {
            this.A = a;
            this.B = b;
        }
    }
}
